﻿using System;
using System.Threading;

namespace Hsenl.Network {
    public class TcpPacketSender : APacketHandler, IPacketSender, IMessageWriter {
        private const int BodySizeBits = 2;
        private const int TotalHeadBits = 2;

        private readonly PacketWriter _packetWriter = new();
        private readonly ReaderWriterLockSlim _writerSlim = new();

        public event Action<Memory<byte>> OnMessageWritedEvent {
            add => this._packetWriter.OnMessageWritedEvent += value;
            remove => this._packetWriter.OnMessageWritedEvent -= value;
        }

        private TcpChannel _channel;
        private TcpSocket _socket;

        protected override void OnInit(Channel channel) {
            this._channel = (TcpChannel)channel;
            this._socket = (TcpSocket)this._channel.UserToken;

            this._packetWriter.Init(BodySizeBits, TotalHeadBits);
            this._packetWriter.OutputEvent += this.Output;
        }

        private void Output() {
            this._socket.SendAsync();
        }

        public void Write(byte[] data, int offset, int count) {
            this.Write(data.AsSpan(offset, count));
        }

        public void Write(Span<byte> data) {
            this.writer_write(data);
        }

        public Memory<byte> GetSendBuffer(int length) {
            var bytes = this.GetSendBuffer(length, out var offset, out var count);
            return bytes.AsMemory(offset, count);
        }

        public byte[] GetSendBuffer(int len, out int offset, out int count) {
            if (this.IsDisposed) {
                offset = 0;
                count = 0;
                return null;
            }

            return this._packetWriter.GetSendBuffer(len, out offset, out count);
        }

        public void FlushWriter() {
            this.writer_update();
        }

        private void writer_write(Span<byte> data) {
            this._writerSlim.EnterWriteLock();
            try {
                this._packetWriter.Write(data, this._channel.Service.AutoSend);
            }
            finally {
                this._writerSlim.ExitWriteLock();
            }
        }

        private void writer_update() {
            this._writerSlim.EnterWriteLock();
            try {
                this._packetWriter.Update();
            }
            finally {
                this._writerSlim.ExitWriteLock();
            }
        }

        protected override void OnDisposed() {
            this._writerSlim.EnterWriteLock();
            try {
                this._packetWriter.Dispose();
            }
            finally {
                this._writerSlim.ExitWriteLock();
            }

            this._channel = null;
            this._socket = null;
        }
    }
}